package gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.util.TimeValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class RestTemplateConfiguration {
  
  private static final int MAX_TOTAL_CONNECTIONS = 300;
  private static final int MAX_CONNECTION_PER_ROUTE = 100;
  private static final int CONNECT_TIMEOUT = 3000;
  private static final int SOCKET_TIMEOUT = 3000;
  private static final long CONNECTION_REQUEST_TIMEOUT = 3000;
  private static final int CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS = 60;
  
  @Bean
  public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
    PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
    poolingConnectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
    poolingConnectionManager.setDefaultMaxPerRoute(MAX_CONNECTION_PER_ROUTE);
    ConnectionConfig connectionConfig = ConnectionConfig.custom()
        .setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
        .setSocketTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS)
        .build();
    poolingConnectionManager.setDefaultConnectionConfig(connectionConfig);
    return poolingConnectionManager;
  }
  
  @Bean
  public CloseableHttpClient httpClient() {
    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).build();
    
    return HttpClients.custom().setDefaultRequestConfig(requestConfig)
        .setConnectionManager(poolingHttpClientConnectionManager()).disableCookieManagement().build();
  }
  
  @Bean
  public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
    restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
    restTemplate.getMessageConverters().set(2, new ResourceHttpMessageConverter());
    return restTemplate;
  }
  
  @Bean
  public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
//    clientHttpRequestFactory.setHttpClient(httpClient());
    return clientHttpRequestFactory;
  }
  
  @Bean
  public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
    return new Runnable() {
      @Override
      @Scheduled(fixedDelay = 60000)
      public void run() {
        try {
          if (connectionManager != null) {
            log.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
            connectionManager.closeExpired();
            connectionManager.closeIdle(TimeValue.ofSeconds(CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS));
          }
        } catch (Exception e) {
          log.error("run IdleConnectionMonitor - Exception occurred.", e);
        }
      }
    };
  }
  
  @Bean
  public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(2);
    return scheduler;
  }
  
}
